<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>HTML DOM - Drag and drop element in a list</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="stylesheet" href="/css/demo.css" />
        <link rel="preconnect" href="https://fonts.gstatic.com" />
        <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css2?family=Inter&family=Source+Code+Pro&display=swap"
        />
        <style>
            .draggable {
                cursor: move;
                margin-bottom: 1rem;
                user-select: none;

                /* Center the content */
                align-items: center;
                display: flex;
                justify-content: center;

                /* Size */
                height: 4rem;
                width: 16rem;

                /* Misc */
                border: 1px solid #cbd5e0;
            }
            .placeholder {
                background-color: #edf2f7;
                border: 2px dashed #cbd5e0;
                margin-bottom: 1rem;
            }
        </style>
    </head>
    <body>
        <div style="align-items: center; display: flex; justify-content: center; padding: 4rem 0">
            <div id="list">
                <div class="draggable">A</div>
                <div class="draggable">B</div>
                <div class="draggable">C</div>
                <div class="draggable">D</div>
                <div class="draggable">E</div>
            </div>
        </div>

        <script>
            document.addEventListener('DOMContentLoaded', function () {
                // Query the list element
                const list = document.getElementById('list');

                let draggingEle;
                let placeholder;
                let isDraggingStarted = false;

                // The current position of mouse relative to the dragging element
                let x = 0;
                let y = 0;

                // Swap two nodes
                const swap = function (nodeA, nodeB) {
                    const parentA = nodeA.parentNode;
                    const siblingA = nodeA.nextSibling === nodeB ? nodeA : nodeA.nextSibling;

                    // Move `nodeA` to before the `nodeB`
                    nodeB.parentNode.insertBefore(nodeA, nodeB);

                    // Move `nodeB` to before the sibling of `nodeA`
                    parentA.insertBefore(nodeB, siblingA);
                };

                // Check if `nodeA` is above `nodeB`
                const isAbove = function (nodeA, nodeB) {
                    // Get the bounding rectangle of nodes
                    const rectA = nodeA.getBoundingClientRect();
                    const rectB = nodeB.getBoundingClientRect();

                    return rectA.top + rectA.height / 2 < rectB.top + rectB.height / 2;
                };

                const mouseDownHandler = function (e) {
                    draggingEle = e.target;

                    // Calculate the mouse position
                    const rect = draggingEle.getBoundingClientRect();
                    x = e.pageX - rect.left;
                    y = e.pageY - rect.top;

                    // Attach the listeners to `document`
                    document.addEventListener('mousemove', mouseMoveHandler);
                    document.addEventListener('mouseup', mouseUpHandler);
                };

                const mouseMoveHandler = function (e) {
                    const draggingRect = draggingEle.getBoundingClientRect();

                    if (!isDraggingStarted) {
                        isDraggingStarted = true;

                        // Let the placeholder take the height of dragging element
                        // So the next element won't move up
                        placeholder = document.createElement('div');
                        placeholder.classList.add('placeholder');
                        draggingEle.parentNode.insertBefore(placeholder, draggingEle.nextSibling);
                        placeholder.style.height = `${draggingRect.height}px`;
                    }

                    // Set position for dragging element
                    draggingEle.style.position = 'absolute';
                    draggingEle.style.top = `${e.pageY - y}px`;
                    draggingEle.style.left = `${e.pageX - x}px`;

                    // The current order
                    // prevEle
                    // draggingEle
                    // placeholder
                    // nextEle
                    const prevEle = draggingEle.previousElementSibling;
                    const nextEle = placeholder.nextElementSibling;

                    // The dragging element is above the previous element
                    // User moves the dragging element to the top
                    if (prevEle && isAbove(draggingEle, prevEle)) {
                        // The current order    -> The new order
                        // prevEle              -> placeholder
                        // draggingEle          -> draggingEle
                        // placeholder          -> prevEle
                        swap(placeholder, draggingEle);
                        swap(placeholder, prevEle);
                        return;
                    }

                    // The dragging element is below the next element
                    // User moves the dragging element to the bottom
                    if (nextEle && isAbove(nextEle, draggingEle)) {
                        // The current order    -> The new order
                        // draggingEle          -> nextEle
                        // placeholder          -> placeholder
                        // nextEle              -> draggingEle
                        swap(nextEle, placeholder);
                        swap(nextEle, draggingEle);
                    }
                };

                const mouseUpHandler = function () {
                    // Remove the placeholder
                    placeholder && placeholder.parentNode.removeChild(placeholder);

                    draggingEle.style.removeProperty('top');
                    draggingEle.style.removeProperty('left');
                    draggingEle.style.removeProperty('position');

                    x = null;
                    y = null;
                    draggingEle = null;
                    isDraggingStarted = false;

                    // Remove the handlers of `mousemove` and `mouseup`
                    document.removeEventListener('mousemove', mouseMoveHandler);
                    document.removeEventListener('mouseup', mouseUpHandler);
                };

                // Query all items
                [].slice.call(list.querySelectorAll('.draggable')).forEach(function (item) {
                    item.addEventListener('mousedown', mouseDownHandler);
                });
            });
        </script>
    </body>
</html>
